package org.zyj.io;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Echo Server 3
 * 第三代 使用selector的NIO:
 * 用一条线程绑定多路复用器来处理所有连接(包括accept事件,read事件,write事件等)
 * ---
 * 优点:
 * 1. 不在需要不停的轮询了, 当发生连接或者读取事件的时候, 是由多路复用器来通知我们(这里可以选择 1.阻塞等待多路复用器 或者 2.轮询查看多路复用器), 节约了CPU资源
 * ---
 * 缺点:
 * 1. 由于只有一个线程在处理连接, 当我们线程处理读写事件的时候, 没法立刻对连接事件作出响应
 * 2. 多个读写事件都在等待一个线程处理, 也就无法将网卡的IO能力完全发挥出来
 */
public class SelectorNIO_1 {

    public static void main(String[] args) throws IOException {
        SocketMultiplexSingleThreadV1 service = new SocketMultiplexSingleThreadV1();
        service.start();
    }


    /**
     * 单线程处理多路复用器的demo
     */
    static class SocketMultiplexSingleThreadV1 {

        private ServerSocketChannel server = null;

        private Selector selector = null;

        private int port = 8080;

        public void initServer() throws IOException {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);//非阻塞
            server.bind(new InetSocketAddress(port));
            selector = Selector.open();//创建一个多路复用器
            server.register(selector, SelectionKey.OP_ACCEPT);//向多路复用器 注册server, 并且设置监听ACCEPT事件
        }

        public void start() throws IOException {
            //1. 启动服务器,同时注册号多路复用器
            initServer();
            while (true) {
                //2. 从多路复用器中拿需要处理的文件描述符
                int readyNum = selector.select();//会阻塞,直到有响应事件, 这个阻塞是针对多路复用器的,要注意!!
                //select()：阻塞到至少有一个通道在你注册的事件上就绪了。
                //select(long timeout)：和select()一样，但最长阻塞时间为timeout毫秒。
                //selectNow()：非阻塞，只要有通道就绪就立刻返回。
                if (readyNum > 0) {
                    //3. 遍历需要处理的key
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();//获取有事件的key, 这个实际上拿到的是多路复用器中的一个成员变量, 搞完只要要把key remove出去, 非常麻烦
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();//这里需要remove掉, 如果不remove掉,下次循环还会遍历到这个key, 设计非常傻逼
                        //4. 处理这个key
                        if (key.isAcceptable()) {
                            acceptHandler(key);//建立连接操作
                        } else if (key.isReadable()) {
                            readHandler(key);//读取数据曹邹
                        }
                    }
                }
            }
        }

        //监听到客户端的连接请求事件之后
        private void acceptHandler(SelectionKey key) throws IOException {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //1. 创建连接
            SocketChannel client = ssc.accept();//建立连接
            client.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
            //2. 将这个连接和一个buffer绑定到一起, buffer当做key的attachment存进去
            //3. 向多路复用器注册监听 该连接的读取事件
            SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ, buffer);//向多路复用器绑定,读取事件
            //这个selectionKey和到时候发生读取事件读取的key是一个对象, 这里暂时没用它
            System.out.println("新客户端: " + client.getRemoteAddress());
        }

        private void readHandler(SelectionKey key) throws IOException {
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();//这个就是在建立连接的时候,当做attachment存进去的buffer
            buffer.clear();
            int read;
            while (true) {
                read = client.read(buffer);
                if (read > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        //这里是把数据写回客户端
                        //这里可以写其他的自己的业务逻辑
                        client.write(buffer);
                    }
                    buffer.clear();
                } else if (read == 0) {
                    break;
                } else { //-1 可能是客户端关闭了, close_wait
                    client.close();
                    break;
                }
            }
        }
    }

}
